package com.ae.shield.generator.builder.recipes.assembly;

import com.google.common.collect.Lists;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.ae.shield.common.recipe.ModRecipesType;
import net.minecraft.advancements.Advancement;
import net.minecraft.advancements.AdvancementRewards;
import net.minecraft.advancements.ICriterionInstance;
import net.minecraft.advancements.IRequirementsStrategy;
import net.minecraft.advancements.criterion.RecipeUnlockedTrigger;
import net.minecraft.data.IFinishedRecipe;
import net.minecraft.fluid.Fluid;
import net.minecraft.item.Item;
import net.minecraft.item.crafting.IRecipeSerializer;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.tags.ITag;
import net.minecraft.util.IItemProvider;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.registries.ForgeRegistries;

import javax.annotation.Nullable;
import java.util.List;
import java.util.function.Consumer;

public abstract class AssemblyRecipeBuilder<T extends AssemblyRecipeBuilder<T>> {
    protected final int count;
    protected final Item result;
    protected final List<Ingredient> ingredients = Lists.newArrayList();
    protected final Advancement.Builder advancementBuilder = Advancement.Builder.builder();
    protected String group;
    protected Ingredient backing;
    protected Fluid fluid;
    protected int fluidCount = 0;
    protected ITag<Fluid> fluidTag;
    protected int energy;
    protected int tick;


    public AssemblyRecipeBuilder(IItemProvider result, int count) {
        this.result = result.asItem();
        this.count = count;
    }

    public T addIngredient(ITag<Item> tagIn) {
        return addIngredient(Ingredient.fromTag(tagIn));
    }

    public T addIngredient(ITag<Item> tagIn, int quantity) {
        return addIngredient(Ingredient.fromTag(tagIn), quantity);
    }

    public T addIngredient(IItemProvider itemIn) {
        return addIngredient(itemIn, 1);
    }

    public T addIngredient(IItemProvider itemIn, int quantity) {
        for (int i = 0; i < quantity; ++i) {
            addIngredient(Ingredient.fromItems(itemIn));
        }

        return (T) this;
    }

    public T addIngredient(Ingredient ingredientIn) {
        return addIngredient(ingredientIn, 1);
    }

    public T addIngredient(Ingredient ingredientIn, int quantity) {
        for (int i = 0; i < quantity; ++i) {
            this.ingredients.add(ingredientIn);
        }

        return (T) this;
    }

    public T addBacking(ITag<Item> tagIn) {
        return addBacking(Ingredient.fromTag(tagIn));
    }

    public T addBacking(IItemProvider item) {
        return addBacking(Ingredient.fromItems(item));
    }

    public T addBacking(Ingredient ingredient) {
        backing = ingredient;
        return (T) this;
    }

    public T addEnergy(int energy) {
        this.energy = energy;
        return (T) this;
    }

    public T addTime(int tick) {
        this.tick = tick;
        return (T) this;
    }

    public T addFluid(Fluid fluid, int count) {
        this.fluid = fluid;
        fluidCount = count;
        return (T) this;
    }

    public T addFluid(ITag<Fluid> fluid, int count) {
        fluidTag = fluid;
        fluidCount = count;
        return (T) this;
    }

    public T addCriterion(String name, ICriterionInstance criterionIn) {
        advancementBuilder.withCriterion(name, criterionIn);
        return (T) this;
    }

    public T setGroup(String groupIn) {
        this.group = groupIn;
        return (T) this;
    }

    public void build(Consumer<IFinishedRecipe> consumer, ResourceLocation id) {
        id = new ResourceLocation(id.getNamespace(), "assembly/" + id.getPath());
        validate(id);
        this.advancementBuilder.withParentId(new ResourceLocation("recipes/root"))
                .withCriterion("has_the_recipe", RecipeUnlockedTrigger.create(id))
                .withRewards(AdvancementRewards.Builder.recipe(id)).withRequirementsStrategy(IRequirementsStrategy.OR);
        consumer.accept(getResult(id));
    }

    abstract public IFinishedRecipe getResult(ResourceLocation id);

    public void validate(ResourceLocation id) {
        if (backing == null) {
            throw new IllegalStateException("缺少底料（backing） " + id);
        } else if (energy < 0) {
            throw new IllegalStateException("缺少能量（energy） " + id);
        } else if (tick < 0) {
            throw new IllegalStateException("缺少加工时间（tick） " + id);
        } else if (ingredients.size() > 13) {
            throw new IllegalStateException("材料总数大于13（ingredients） " + id);
        }
    }

    public static class Result implements IFinishedRecipe {
        private final ResourceLocation id;
        private final Item result;
        private final int count;
        private final String group;
        private final Ingredient backing;
        private final List<Ingredient> ingredients;
        private final Fluid fluid;
        private final int fluidCount;
        private final ITag<Fluid> fluidTag;
        private final int energy;
        private final int tick;
        private final Advancement.Builder advancementBuilder;
        private final ResourceLocation advancementId;

        public Result(ResourceLocation id, Item result, int count, String group, Ingredient backing, List<Ingredient> ingredients, @Nullable Fluid fluid, @Nullable ITag<Fluid> fluidTag, int fluidCount, int energy, int tick, Advancement.Builder advancementBuilder) {
            this.id = id;
            this.result = result;
            this.count = count;
            this.group = group;
            this.backing = backing;
            this.ingredients = ingredients;
            this.fluid = fluid;
            this.fluidTag = fluidTag;
            this.fluidCount = fluidCount;
            this.energy = energy;
            this.tick = tick;
            this.advancementBuilder = advancementBuilder;
            this.advancementId = new ResourceLocation(id.getNamespace(), "recipes/" + id.getPath());
        }


        @Override
        public void serialize(JsonObject json) {
            if (!this.group.isEmpty()) {
                json.addProperty("group", this.group);
            }

            JsonArray backingJson = new JsonArray();
            backingJson.add(backing.serialize());
            json.add("backing", backingJson);

            JsonArray jsonarray = new JsonArray();

            for (Ingredient ingredient : this.ingredients) {
                jsonarray.add(ingredient.serialize());
            }

            json.add("ingredients", jsonarray);

            JsonObject fluidJson = new JsonObject();
            boolean hasFluid = false;

            if (fluid != null) {
                 fluidJson.addProperty("fluid", String.valueOf(ForgeRegistries.FLUIDS.getKey(fluid)));
                 hasFluid = true;
            } else if (fluidTag != null){
                fluidJson.addProperty("fluid_tag", ((ITag.INamedTag<Fluid>)fluidTag).getName().toString());
                hasFluid = true;
            }

            fluidJson.addProperty("count", fluidCount);
            if (hasFluid) {
                json.add("fluid_ingredient", fluidJson);
            }

            JsonObject jsonobject = new JsonObject();
            jsonobject.addProperty("item", String.valueOf(ForgeRegistries.ITEMS.getKey(this.result)));

            if (this.count > 1) {
                jsonobject.addProperty("count", this.count);
            }

            json.add("result", jsonobject);

            json.addProperty("processing_time", tick);
            json.addProperty("energy_per_tick", energy);
        }

        @Override
        public ResourceLocation getID() {
            return id;
        }

        @Override
        public IRecipeSerializer<?> getSerializer() {
            return ModRecipesType.ASSEMBLY_SERIALIZER.get();
        }

        @Nullable
        @Override
        public JsonObject getAdvancementJson() {
            return advancementBuilder.serialize();
        }

        @Nullable
        @Override
        public ResourceLocation getAdvancementID() {
            return advancementId;
        }
    }
}
